// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:engine_tool/src/typed_json.dart';
import 'package:litetest/litetest.dart';

void main() {
  group('JsonObject.string', () {
    test('returns string value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(jsonObject.string('key'), 'value');
    });

    test('throws due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.string('missing'), throwsA(isInstanceOf<MissingKeyJsonReadException>()));
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(() => jsonObject.string('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.integer', () {
    test('returns integer value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(jsonObject.integer('key'), 42);
    });

    test('throws due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(() => jsonObject.integer('missing'), throwsA(isInstanceOf<MissingKeyJsonReadException>()));
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.integer('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.boolean', () {
    test('returns boolean value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': true});
      expect(jsonObject.boolean('key'), true);
    });

    test('throws due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': true});
      expect(() => jsonObject.boolean('missing'), throwsA(isInstanceOf<MissingKeyJsonReadException>()));
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.boolean('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.stringList', () {
    test('returns string list value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 'value2']});
      expect(jsonObject.stringList('key'), <String>['value1', 'value2']);
    });

    test('throws due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 'value2']});
      expect(() => jsonObject.stringList('missing'), throwsA(isInstanceOf<MissingKeyJsonReadException>()));
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.stringList('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });

    test('throws due to wrong element type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 42]});
      expect(() => jsonObject.stringList('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.stringOrNull', () {
    test('returns string value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(jsonObject.stringOrNull('key'), 'value');
    });

    test('returns null due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(jsonObject.stringOrNull('missing'), isNull);
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(() => jsonObject.stringOrNull('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.integerOrNull', () {
    test('returns integer value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(jsonObject.integerOrNull('key'), 42);
    });

    test('returns null due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 42});
      expect(jsonObject.integerOrNull('missing'), isNull);
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.integerOrNull('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.booleanOrNull', () {
    test('returns boolean value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': true});
      expect(jsonObject.booleanOrNull('key'), true);
    });

    test('returns null due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': true});
      expect(jsonObject.booleanOrNull('missing'), isNull);
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.booleanOrNull('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.stringListOrNull', () {
    test('returns string list value', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 'value2']});
      expect(jsonObject.stringListOrNull('key'), <String>['value1', 'value2']);
    });

    test('returns null due to missing key', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 'value2']});
      expect(jsonObject.stringListOrNull('missing'), isNull);
    });

    test('throws due to wrong type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': 'value'});
      expect(() => jsonObject.stringListOrNull('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });

    test('throws due to wrong element type', () {
      const JsonObject jsonObject = JsonObject(<String, Object?>{'key': <Object?>['value1', 42]});
      expect(() => jsonObject.stringListOrNull('key'), throwsA(isInstanceOf<InvalidTypeJsonReadException>()));
    });
  });

  group('JsonObject.map', () {
    test('returns multiple fields', () {
      final (String name, int age, bool isStudent) = const JsonObject(<String, Object?>{
        'name': 'Alice',
        'age': 42,
        'isStudent': true,
      }).map((JsonObject json) {
        return (
          json.string('name'),
          json.integer('age'),
          json.boolean('isStudent'),
        );
      });

      expect(name, 'Alice');
      expect(age, 42);
      expect(isStudent, true);
    });

    test('throws due to missing keys', () {
      try {
        const JsonObject(<String, Object?>{
          'age': 42,
        }).map((JsonObject json) {
          return (
            json.string('name'),
            json.integer('age'),
            json.boolean('isStudent'),
          );
        });
        fail('Expected JsonMapException');
      } on JsonMapException catch (e) {
        expect(
          e.exceptions.map((JsonReadException e) => (e as MissingKeyJsonReadException).key).toList(),
          containsStringsInOrder(<String>['name', 'isStudent'],
        ));
      }
    });

    test('throws due to wrong types', () {
      try {
        const JsonObject(<String, Object?>{
          'name': 42,
          'age': '42',
          'isStudent': false,
        }).map((JsonObject json) {
          return (
            json.string('name'),
            json.integer('age'),
            json.boolean('isStudent'),
          );
        });
        fail('Expected JsonMapException');
      } on JsonMapException catch (e) {
        expect(
          e.exceptions.map((JsonReadException e) => switch (e) {
            final InvalidTypeJsonReadException e => e.key,
            final MissingKeyJsonReadException e => e.key,
            _ => throw StateError('Unexpected exception type: $e'),
          }).toList(),
          containsStringsInOrder(<String>['name', 'age'],
        ));
      }
    });

    test('allows a default with onError', () {
      final (String name, int age, bool isStudent) = const JsonObject(<String, Object?>{
        'name': 'Alice',
        'age': 42,
        'isStudent': 'true',
      }).map((JsonObject json) {
        return (
          json.string('name'),
          json.integer('age'),
          json.boolean('isStudent'),
        );
      }, onError: expectAsync2((_, JsonMapException e) {
        expect(
          e.exceptions.map((JsonReadException e) => switch (e) {
            final InvalidTypeJsonReadException e => e.key,
            final MissingKeyJsonReadException e => e.key,
            _ => throw StateError('Unexpected exception type: $e'),
          }).toList(),
          <String>['isStudent'],
        );
        return ('Bob', 0, false);
      }));

      expect(name, 'Bob');
      expect(age, 0);
      expect(isStudent, false);
    });

    test('disallows a return type of Future<*>', () {
      expect(() {
        const JsonObject(<String, Object?>{
          'name': 'Alice',
          'age': 42,
          'isStudent': true,
        }).map((JsonObject json) async {
          return (
            json.string('name'),
            json.integer('age'),
            json.boolean('isStudent'),
          );
        });
      }, throwsA(isInstanceOf<ArgumentError>()));
    });
  });
}
